package com.flow.framework.common.json;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.flow.framework.common.constant.FrameworkCommonConstant;
import com.flow.framework.common.error.SystemErrorCode;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.common.type.TypeReference;
import com.flow.framework.common.util.verify.VerifyUtil;

import java.io.Serializable;
import java.util.*;

/**
 * jsonObject适配
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/1/2
 */
public class JsonObject implements Map, Serializable {
    private static final long serialVersionUID = -54719329954411374L;

    private Map<String, Object> properties = new LinkedHashMap<>();

    public JsonObject() {
    }

    private static ObjectMapper getObjectMapper() {
        return ObjectMapperHolder.getInstance();
    }

    public static JsonObject fromObject(Object obj) {
        if (null == obj) {
            return new JsonObject();
        } else if (obj instanceof JsonObject) {
            return (JsonObject) obj;
        }
        JsonObject json = new JsonObject();
        try {
            Map<String, Object> ret;
            if (obj instanceof String) {
                ret = getObjectMapper().readValue(obj.toString(),
                        new TypeReference<LinkedHashMap<String, Object>>() {
                        });
            } else {
                String jsonString = getObjectMapper().writeValueAsString(obj);
                ret = getObjectMapper().readValue(jsonString,
                        new TypeReference<LinkedHashMap<String, Object>>() {
                        });
            }

            if (null != ret) {
                convertProperty(json, ret, null);
            }
            return json;
        } catch (Exception e) {
            throw new CheckedException(SystemErrorCode.SERIALIZE_ERROR, "fromObject failed.", e);
        }
    }

    /**
     * 如果传递的参数为string，则直接返回传入的字符串，如果为null，则返回null
     *
     * @param obj obj
     * @return
     */
    public static String toString(Object obj) {
        try {
            return getObjectMapper().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new CheckedException(SystemErrorCode.SERIALIZE_ERROR, "object to json string is empty.", e);
        }
    }

    private static void convertProperty(JsonObject json, Map<String, Object> ret, Set<String> ignoreProperties) {
        Iterator<Entry<String, Object>> iterator = ret.entrySet().iterator();
        while (true) {
            Entry<String, Object> entry;
            String key;
            do {
                if (!iterator.hasNext()) {
                    return;
                }
                entry = iterator.next();
                key = entry.getKey();
            } while (null != ignoreProperties && ignoreProperties.contains(key));

            Object value = entry.getValue();
            Object tempValue = transformValueToJson(value);
            json.properties.put(entry.getKey(), tempValue);
        }
    }

    private static Object transformValueToJson(Object value) {
        Object tempValue = value;
        if (value instanceof Map && !(value instanceof JsonObject)) {
            tempValue = fromObject(value);
        } else if (value instanceof Collection && !(value instanceof JsonArray)) {
            tempValue = JsonArray.fromObject(value);
        }
        return tempValue;
    }

    public static JsonObject fromObject(Object obj, String[] ignoreProperties) {
        Set<String> ignoreSet = new HashSet<>();
        if (null != ignoreProperties) {
            int length = ignoreProperties.length;
            ignoreSet.addAll(Arrays.asList(ignoreProperties).subList(0, length));
        }
        return fromObject(obj, ignoreSet);
    }

    public static JsonObject fromObject(Object obj, Set<String> ignoreProperties) {
        if (null == obj) {
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR, "fromObject param is empty.");
        }
        JsonObject json = new JsonObject();
        try {
            Map<String, Object> ret;
            ObjectWriter objWriter = getObjectMapper().writer();

            if (obj instanceof String) {
                ret = getObjectMapper().readValue(obj.toString(), new TypeReference<LinkedHashMap<String, Object>>() {
                });
            } else {
                String jsonString = objWriter.writeValueAsString(obj);
                ret = getObjectMapper().readValue(jsonString, new TypeReference<LinkedHashMap<String, Object>>() {
                });
            }
            if (null != ret) {
                convertProperty(json, ret, ignoreProperties);
            }
            return json;
        } catch (Exception e) {
            throw new CheckedException(SystemErrorCode.SERIALIZE_ERROR, "fromObject param is empty.", e);
        }
    }

    public static <T> T toBean(JsonObject jsonObject, Class<T> beanClass) {
        return toBean(jsonObject.toString(), beanClass);
    }

    public static <T> T toBean(JsonObject jsonObject, TypeReference<T> typeReference) {
        return toBean(jsonObject.toString(), typeReference);
    }

    public static <T> T toBean(String jsonStr, Class<T> clazz) {
        if (VerifyUtil.hasEmpty(jsonStr, clazz)) {
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR, "parameter error.");
        }
        try {
            return getObjectMapper().readValue(jsonStr, clazz);
        } catch (Exception e) {
            throw new CheckedException(SystemErrorCode.SERIALIZE_ERROR, "json to bean failed.", e);
        }
    }

    public static <T> T toBean(String jsonStr, TypeReference<T> typeReference) {
        if (VerifyUtil.hasEmpty(jsonStr, typeReference)) {
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR, "parameter error.");
        }
        try {
            return getObjectMapper().readValue(jsonStr, typeReference);
        } catch (Exception e) {
            throw new CheckedException(SystemErrorCode.SERIALIZE_ERROR, "json to bean failed.", e);
        }
    }

    public Object get(String key) {
        if (!VerifyUtil.isEmpty(key)) {
            return this.properties.get(key);
        }
        throw new CheckedException(SystemErrorCode.PARAMS_ERROR, "the key is empty.");
    }

    public Boolean getBoolean(String key) {
        if (!VerifyUtil.isEmpty(key)) {
            Object value = this.properties.get(key);
            if (null != value) {
                if (isFalse(value)) {
                    return false;
                }
                if (isTrue(value)) {
                    return true;
                }
            } else {
                return null;
            }
        }
        throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
    }

    static boolean isFalse(Object value) {
        return Boolean.FALSE.equals(value) || value instanceof String && String.valueOf(false).equalsIgnoreCase((String) value);
    }

    static boolean isTrue(Object value) {
        return Boolean.TRUE.equals(value) || value instanceof String && String.valueOf(true).equalsIgnoreCase((String) value);
    }

    public Double getDouble(String key) {
        if (!VerifyUtil.isEmpty(key)) {
            Object value = this.properties.get(key);
            if (null != value) {
                try {
                    return value instanceof Number ? ((Number) value).doubleValue() : Double.parseDouble(value.toString());
                } catch (Exception e) {
                    throw new CheckedException(SystemErrorCode.SERIALIZE_ERROR, "getDouble failed.", e);
                }
            }
            return null;
        }
        throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
    }

    public Integer getInt(String key) {
        if (!VerifyUtil.isEmpty(key)) {
            Object o = this.get(key);
            if (o != null) {
                if (o instanceof Integer) {
                    return (Integer) o;
                } else {
                    throw new CheckedException(SystemErrorCode.DATA_TYPE_ERROR);
                }
            } else {
                return null;
            }
        }
        throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
    }

    public Long getLong(String key) {
        if (!VerifyUtil.isEmpty(key)) {
            Object o = this.get(key);
            if (o != null) {
                if (o instanceof Long) {
                    return (Long) o;
                } else {
                    throw new CheckedException(SystemErrorCode.DATA_TYPE_ERROR);
                }
            } else {
                return null;
            }
        }
        throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
    }

    public String getString(String key) {
        if (!VerifyUtil.isEmpty(key)) {
            if (!this.properties.containsKey(key)) {
                return null;
            } else {
                Object o = this.get(key);
                return o != null ? o.toString() : null;
            }
        }
        throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
    }

    @Override
    public String toString() {
        try {
            return getObjectMapper().writeValueAsString(this.properties);
        } catch (JsonProcessingException e) {
            throw new CheckedException(SystemErrorCode.SERIALIZE_ERROR, "toString failed.", e);
        }
    }

    public JsonObject getJsonObject(String key) {
        if (!VerifyUtil.isEmpty(key)) {
            Object o = this.get(key);
            if (null != o) {
                JsonObject json;
                if (!(o instanceof JsonObject)) {
                    json = fromObject(o);
                } else {
                    json = (JsonObject) o;
                }
                return json;
            }
            return null;
        }
        throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
    }

    public JsonArray getJsonArray(String key) {
        if (!VerifyUtil.isEmpty(key)) {
            Object o = this.get(key);
            if (null != o) {
                JsonArray array;
                if (!(o instanceof JsonArray)) {
                    array = JsonArray.fromObject(o);
                } else {
                    array = (JsonArray) o;
                }
                return array;
            }
            return null;
        }
        throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
    }

    @Override
    public void clear() {
        this.properties.clear();
    }

    @Override
    public boolean containsKey(Object key) {
        return this.properties.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return this.properties.containsValue(value);
    }

    @Override
    public Set<Map.Entry<String, Object>> entrySet() {
        return this.properties.entrySet();
    }

    @Override
    public Object get(Object key) {
        return this.properties.get(key);
    }

    @Override
    public boolean isEmpty() {
        return this.properties.isEmpty();
    }

    public Iterator<String> keys() {
        return this.properties.keySet().iterator();
    }

    @Override
    public Set<String> keySet() {
        return this.properties.keySet();
    }

    @Override
    public Object put(Object key, Object value) {
        if (null == key) {
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
        } else if (null == value) {
            return this;
        }
        Object tempValue = value;
        if (value instanceof String) {
            tempValue = this.convertStr2Json(value);
        } else if (value instanceof Map && !(value instanceof JsonObject)) {
            tempValue = fromObject(value);
        } else if (value instanceof Collection && !(value instanceof JsonArray)) {
            tempValue = JsonArray.fromObject(value);
        }
        return this.properties.put(String.valueOf(key), tempValue);
    }

    private Object convertStr2Json(Object value) {
        Object tempValue = value;
        try {
            if (((String) value).startsWith(FrameworkCommonConstant.LEFT_BRACE)
                    && ((String) value).endsWith(FrameworkCommonConstant.RIGHT_BRACE)) {
                tempValue = fromObject(value);
            } else if (((String) value).startsWith(FrameworkCommonConstant.LEFT_BRACKET)
                    && ((String) value).endsWith(FrameworkCommonConstant.RIGHT_BRACKET)) {
                tempValue = JsonArray.fromObject(value);
            }
        } catch (Exception e) {
            throw new CheckedException(SystemErrorCode.SERIALIZE_ERROR, "can't convert string to JSON Object.", e);
        }
        return tempValue;
    }

    @Override
    public void putAll(Map map) {
        if (null == map) {
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR, "map is null.");
        }
        Iterator iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            Entry entry = (Entry) obj;
            Object key = entry.getKey();
            Object value = entry.getValue();
            this.put(key, value);
        }
    }

    @Override
    public Object remove(Object key) {
        return this.properties.remove(key);
    }

    @Override
    public int size() {
        return this.properties.size();
    }

    @Override
    public Collection values() {
        return this.properties.values();
    }
}